use graphics::math::{add, mul_scalar, Vec2d}; //vec2d,提供二位向量的数学运算和转换功能
use piston_window::*; //创建gui程序工具
use rand::prelude::*;
use std::alloc::{GlobalAlloc, Layout, System}; //控制内存分配工具
use std::time::Instant; //系统时钟访问

#[global_allocator] //标识注解的值满足GlobalAlloc trait
static ALLOCATOR: ReportingAllocator = ReportingAllocator;
struct ReportingAllocator; //把每次分配内存的事件输出到标准输出

unsafe impl GlobalAlloc for ReportingAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let start = Instant::now();
        let ptr = System.alloc(layout); //实际分配工作交给系统
        let end = Instant::now();
        let time_taken = end - start;
        let bytes_requested = layout.size();

        eprintln!("{}\t{}", bytes_requested, time_taken.as_nanos());
        ptr
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout);
    }
}

/// 整个生命周期都会用到的数据
struct World {
    current_turn: u64,
    particles: Vec<Box<Particle>>,
    height: f64,
    width: f64,
    rng: ThreadRng,
}

/// 二维空间对象
struct Particle {
    height: f64,
    width: f64,
    position: Vec2d<f64>,
    velocity: Vec2d<f64>,
    acceleration: Vec2d<f64>,
    color: [f32; 4],
}
impl Particle {
    fn new(world: &World) -> Particle {
        let mut rng = thread_rng();
        let x = rng.gen_range(0.0..world.width); //从窗口底部的一个随机位置开始
        let y = world.height;
        let x_velocity = 0.0;
        let y_velocity = rng.gen_range(-2.0..0.0); //随时间垂直上升
        let x_acceleration = 0.0;
        let y_acceleration = rng.gen_range(-2.0..0.0); //随时间加速上升

        Particle {
            height: 4.0,
            width: 4.0,
            position: [x, y].into(),
            velocity: [x_velocity, y_velocity].into(),
            acceleration: [x_acceleration, y_acceleration].into(), //into()把[f64;2]转换为Vec2d
            color: [1.0, 1.0, 1.0, 0.99],                          //插入完全饱和透明度极低的白色
        }
    }

    fn update(&mut self) {
        self.velocity = add(self.velocity, self.acceleration);
        self.position = add(self.position, self.velocity); //把某个点移动到下一个位置
        self.acceleration = mul_scalar(self.acceleration, 0.7); //降低点在屏幕上移动速度的增长率
        self.color[3] *= 0.995;
    }
}
impl World {
    fn new(width: f64, height: f64) -> World {
        World {
            current_turn: 0,
            particles: Vec::<Box<Particle>>::new(),
            height: height,
            width: width,
            rng: thread_rng(),
        }
    }

    fn add_shapes(&mut self, n: i32) {
        for _ in 0..n.abs() {
            let particle = Particle::new(&self); //创建一个Particle,作为局部变量保存在栈上
            let boxed_particle = Box::new(particle); //获取particle所有权，数据移动到堆上，创建引用
            self.particles.push(boxed_particle);
        }
    }

    fn remove_shapes(&mut self, n: i32) {
        for _ in 0..n.abs() {
            let mut to_delete = None;
            //particle_iter被分割为自己的变量，以便更容易的适配页面
            let particle_iter = self.particles.iter().enumerate();

            //在n次迭代中，每次都删除其中第一个不可见的点，如果没有不可见的点，则删除最早创建的那个点
            for (i, particle) in particle_iter {
                if particle.color[3] < 0.02 {
                    to_delete = Some(i);
                }
                break;
            }
            if let Some(i) = to_delete {
                self.particles.remove(i);
            } else {
                self.particles.remove(0);
            }
        }
    }

    fn update(&mut self) {
        let n = self.rng.gen_range(-3..=3); //返回一个从(-3,3]的随机整数
        if n > 0 {
            self.add_shapes(n);
        } else {
            self.remove_shapes(n);
        }

        self.particles.shrink_to_fit();
        for shape in &mut self.particles {
            shape.update();
        }
        self.current_turn += 1;
    }
}
fn main() {
    let (width, height) = (1280.0, 960.0);
    let mut window: PistonWindow = WindowSettings::new("particles", [width, height])
        .exit_on_esc(true)
        .build()
        .expect("Could not create a window");

    let mut world = World::new(width, height);
    world.add_shapes(1000);
    while let Some(event) = window.next() {
        world.update();

        window.draw_2d(&event, |ctx, renderer, _device| {
            clear([0.15, 0.17, 0.17, 0.9], renderer);

            for s in &mut world.particles {
                let size = [s.position[0], s.position[1], s.width, s.height];
                rectangle(s.color, size, ctx.transform, renderer);
            }
        });
    }
}
